﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Diagnostics;

namespace DependencyParse.Library
{
    public class AssemblyHelper
    {
        static Dictionary<string, AssemblyHelper> Assem = new Dictionary<string, AssemblyHelper>();
        private string mKey = string.Empty;

        public static AssemblyHelper Load(string assemblyPath)
        {
            if (string.IsNullOrEmpty(assemblyPath))
                throw new FileNotFoundException();

            string path = Path.GetFullPath(assemblyPath);
            if (string.IsNullOrEmpty(path))
                throw new FileNotFoundException(assemblyPath);

            AssemblyHelper helper = null;
            if (Assem.TryGetValue(path, out helper))
                return helper;
            else
            {
                helper = new AssemblyHelper(path);
                Assem.Add(path, helper);
                return helper;
            }
        }


        private AssemblyHelper(string assemblyPath)
        {
            Interfaces = new List<InterfaceEntity>();
            Classes = new List<ClassEntity>();

            Assembly asm;
            foreach (var assemblyName in Directory.GetFiles(assemblyPath, "*.dll"))
            {
                if (LoadAssembly(assemblyName, out asm))
                {
                    Interfaces.AddRange(LoadInterfaces(asm));
                    Classes.AddRange(LoadClasses(asm));
                }
            }

            foreach (var assemblyName in Directory.GetFiles(assemblyPath, "*.exe"))
            {
                if (LoadAssembly(assemblyName, out asm))
                {
                    Interfaces.AddRange(LoadInterfaces(asm));
                    Classes.AddRange(LoadClasses(asm));
                }
            }

        }


        private bool LoadAssembly(string assemblyName, out Assembly asm)
        {
            try
            {
                asm = Assembly.ReflectionOnlyLoadFrom(assemblyName);
                if (asm == null)
                    return false;
            }
            catch
            {
                asm = null;
                return false;
            }

            foreach (var otherasm in asm.GetReferencedAssemblies())
            {
                try
                {
                    Assembly.ReflectionOnlyLoad(otherasm.FullName);
                }
                catch
                {
                    Assembly assembly = null;

                    try
                    {
                        assembly = Assembly.ReflectionOnlyLoadFrom(Path.Combine(Path.GetDirectoryName(assemblyName), otherasm.Name + ".dll"));
                    }
                    catch
                    {
                        //throw new ApplicationException(string.Format("the assembly {0} is missing", otherasm.Name));
                    }

                    if (assembly == null)
                    {
                        try
                        {
                            assembly = Assembly.ReflectionOnlyLoadFrom(Path.Combine(Path.GetDirectoryName(assemblyName), otherasm.Name + ".exe"));
                        }
                        catch
                        {
                            //throw new ApplicationException(string.Format("the assembly {0} is missing", otherasm.Name));
                        }
                    }

                    if (assembly == null)
                    {
                        //throw new ApplicationException(string.Format("the assembly {0} is missing", otherasm.Name));
                    }
                }
            }
            return true;
        }

        public static List<InterfaceEntity> LoadInterfaces(Assembly asm)
        {
            List<InterfaceEntity> interfaces = new List<InterfaceEntity>();


            foreach (var t in asm.GetTypes())
            {
                try
                {
                    if (t.IsInterface)
                        interfaces.Add(new InterfaceEntity
                        {
                            Name = t.Name,
                            AssemblyName = asm.FullName,
                            Namespace = t.Namespace
                        });
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                }
            }


            return interfaces;
        }

        public static List<ClassEntity> LoadClasses(Assembly asm)
        {
            List<ClassEntity> entities = new List<ClassEntity>();


            foreach (var t in asm.GetTypes())
            {
                try
                {
                    if (t.IsClass)
                        entities.Add(
                            new ClassEntity
                            {
                                AssemblyName = asm.FullName,
                                Name = t.Name,
                                Namespace = t.Namespace,
                                ImplementedInterfaces = LoadImplementInterfaces(t),
                                ReferencedDependencyInterfaces = LoadReferencedDependencyInterfaces(t),
                            }
                            );
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                }
            }


            return entities;
        }

        public static List<InterfaceEntity> LoadImplementInterfaces(Type type)
        {
            var its = type.GetInterfaces();
            List<InterfaceEntity> iList = new List<InterfaceEntity>();
            foreach (var i in its)
            {
                iList.Add(new InterfaceEntity
                {
                    AssemblyName = i.Assembly.FullName,
                    Name = i.Name,
                    Namespace = i.Namespace
                });
            }

            return iList;
        }

        private const string DependencyFlag = "[Microsoft.Practices.Unity.DependencyAttribute()]";
        public static List<InterfaceEntity> LoadReferencedDependencyInterfaces(Type type)
        {
            var properties = type.GetProperties(BindingFlags.GetProperty | BindingFlags.SetProperty
                | BindingFlags.Public | BindingFlags.Instance);

            List<InterfaceEntity> its = new List<InterfaceEntity>();

            foreach (var p in properties)
            {
                try
                {
                    if (!p.PropertyType.IsInterface)
                        continue;


                    var ats = CustomAttributeData.GetCustomAttributes(p);
                    foreach (var at in ats)
                    {
                        if (at.ToString() == DependencyFlag)
                        {
                            its.Add(
                                                    new InterfaceEntity
                                                    {
                                                        AssemblyName = p.PropertyType.Assembly.FullName,
                                                        Name = p.PropertyType.Name,
                                                        Namespace = p.PropertyType.Namespace
                                                    }
                                                    );
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                }
            }

            return its;
        }

        public List<InterfaceEntity> Interfaces
        {
            get;
            private set;
        }

        public List<ClassEntity> Classes
        {
            get;
            private set;
        }
    }
}
